Skip to content

Recovery rates & healing spells: rest/meditation/ley draw, treatment days, spell targets#42

Merged
StreamDemon merged 6 commits into
mainfrom
feat/recovery-healing
Jul 6, 2026
Merged

Recovery rates & healing spells: rest/meditation/ley draw, treatment days, spell targets#42
StreamDemon merged 6 commits into
mainfrom
feat/recovery-healing

Conversation

@StreamDemon

@StreamDemon StreamDemon commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Closes #41.

The book-rules layer on top of #40's living-vitals plumbing: recovery rates and the healing-spell shape. Page references verified against the rendered rulebook (printed page = PDF page − 3).

Rules (@riftforge/rules)

  • content/combat/recovery.json — page-stamped book defaults: P.P.E. 5/hr rest, 10/hr meditation, ley draw 10/melee on a line and 20 at a nexus ("Recovery of P.P.E.", p.186); treatment 2 H.P. + 4 S.D.C./day non-professional, 2-then-4 H.P. + 6 S.D.C./day professional ("Battle Injuries & Recovery", p.354).
  • engine/recovery.ts — pure fns returning raw amounts (clamping stays at the write, like everything else):
    • restRecovery(hours, mode, occ?) — honors the O.C.C.'s printed rates (the Ley Line Walker rests at 7/15).
    • leyLineDraw(melees, atNexus, occ?) — honors the override (the Walker draws double: 20/40).
    • treatmentRecovery(days, professional, daysAlreadyTreated) — closed-form professional ramp (no per-day loop), daysAlreadyTreated places the days inside the 2-day ramp window.
    • Elapsed time is GM-adjudicated input (whole non-negative counts), never a wall clock — the engine stays deterministic.
  • spellSchema.healing — optional structured field ({ hitPoints?, sdc?, target: self|touch|ranged }) parallel to the printed damage string, dice load-validated; rollSpellHealing(spell, rng) rolls it. The L1–4 catalog has no healing spell — real ones (Heal Wounds L6 …) arrive with Complete the spell list (levels 5-15) #13, which is now content-only.

Backend

  • rest / treat / leyLineDraw mutations — the client names TIME (hours, days, melees); the server derives points from the validated rates and lands them through one shared clamped heal path (healPools, also now backing heal). Returns post-clamp gained so telemetry reports what actually landed.
  • castSpell gains targetId (defaults to the caster): spends the caster's P.P.E., rolls healing dice server-side, and applies them to the target document — cross-document writes in one transaction, so a cast that can't land spends nothing. First table-shaped interaction (VTT groundwork). Non-healing spells refuse a foreign target; target: "self" spells refuse one too.
  • Healing-cast paths are covered end-to-end in healing-cast.test.ts via a surgical catalog graft (synthetic healing spells over the real rules layer) — switches to real content and drops the mock when Complete the spell list (levels 5-15) #13 lands.

Web

  • RECOVERY group in the telemetry rail: > Rest / > Meditate with an hours input, > Ley Draw (one click per melee) with a cyan NEXUS toggle, > Treatment Day N with a PRO toggle (session-local day counter drives the professional ramp).
  • House-voice telemetry: > REST :: 1 HR — P.P.E. +7 [111/134], > LEY DRAW (NEXUS) :: +15 P.P.E. [134/134], > TREATMENT :: DAY 2 (PRO) — H.P. +2 · S.D.C. +6. Cast lines report landed heals when a spell has them.

Validation

  • vp check clean; 211 tests (was 184): recovery rates/ramp/overrides, schema refinements, mutation clamps + atomicity, healing casts e2e.
  • Browser QA on the live sheet (LLW): treatment day 1 → +2/+4, day 2 PRO → +2/+6, rest 1 HR → +7 (O.C.C. override, not the default 5), ley draw → +20, nexus → +40 with honest post-clamp reporting, no console errors.

Out of scope (per the issue): blood-loss ticks, coma/death resuscitation, M.D.C., P.P.E. batteries, special-time ley multipliers.


Summary by cubic

Implements book recovery rates and healing spell targets so characters can rest, meditate, draw from ley lines, receive treatment, and cast targeted heals with atomic, server-validated effects. Treatment courses persist, support GM day overrides, and now auto-end when H.P./S.D.C. hit max. Fulfills #41 across @riftforge/rules, backend mutations, and the sheet UI.

  • New Features

    • Rules: page-stamped defaults for P.P.E. and treatment; engine fns restRecovery, leyLineDraw, treatmentRecovery; structured spell.healing { hitPoints?, sdc?, target } and rollSpellHealing; character current.treatmentDays with sheet vitals.treatmentDays.
    • Backend: new mutations rest, leyLineDraw, treat (client names time; server derives/clamps; returns gained); treat persists and advances treatmentDays, accepts optional day override; shared clamped heal path now auto-clears treatmentDays when both pools reach their maximums (covers treatment, manual heals, and healing casts); castSpell accepts optional targetId and heals the target while spending from the caster in one transaction.
    • Web: RECOVERY controls in the telemetry rail (Rest/Meditate with hours input, Ley Draw with Nexus toggle, Treatment Day with Pro toggle and an editable Day input synced to the stored course — blank shows next day) with post-clamp reporting, including landed heals for healing casts.
    • Tests: recovery rate/unit tests, persisted treatment course + GM override, auto-clear on full mend, and end-to-end targeted healing casts.
  • Bug Fixes

    • Web: serialize Treatment Day clicks with an in-flight guard; gate is per-dossier and released by a monotonic token, resets on route change so stale settles can’t unlock or double-apply.

Written for commit 9c949be. Summary will update on new commits.

Review in cubic

…atment days, spell targets

Rules: page-stamped recovery.json (RUE pp.186/354), restRecovery/
leyLineDraw/treatmentRecovery engine fns with O.C.C. overrides, structured
spell healing field + rollSpellHealing.
Backend: rest/treat/leyLineDraw mutations (client names time, server owns
rates), castSpell targetId with cross-document heals in one transaction,
shared clamped heal path.
Web: RECOVERY group in the telemetry rail (rest/meditate, ley draw with
nexus toggle, treatment day with professional toggle).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/pages/character-sheet.tsx">

<violation number="1" location="apps/web/src/pages/character-sheet.tsx:260">
P2: Rapid repeated clicks on Treatment can send duplicate `daysAlreadyTreated` values, so professional ramp progression can be wrong for consecutive day applications. This happens because `day` is read before the async mutation and there is no in-flight lock; consider guarding concurrent calls (or otherwise serializing day advancement) before issuing `treatMutation`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/web/src/pages/character-sheet.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/web/src/pages/character-sheet.tsx
…e settles can't release the new dossier's lock

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread apps/web/src/pages/character-sheet.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Requires human review: Implements core recovery mechanics and healing spell targets with new mutations and cross-document writes. Given the risk of breakage in game logic, human review is recommended.

Re-trigger cubic

…ative day counter + GM day override

current.treatmentDays drives the professional ramp and survives reloads;
restoreVitals/rollVitals clear it with the pools (fresh pools, fresh
course). treat() reads and advances the stored counter; optional day arg
is the GM override for declaring a new course. The rail's day counter is
now an editable DAY input synced to the stored course.
@StreamDemon

Copy link
Copy Markdown
Owner Author

Follow-up to the review discussion: the treatment course is now persisted on the document (current.treatmentDays) instead of a session-local click counter. The server reads and advances it in treat (the ramp survives page reloads), restoreVitals/rollVitals clear it with the pools, and the rail's day counter became an editable DAY input — the GM override for declaring a new injury course (treat({ day })). Verified live: day 3 pays the ramped 4 H.P. after a reload; override to day 1 pays 2. 212 tests.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 6 files (changes from recent commits).

Requires human review: Core business logic changes: new recovery engine (P.P.E./H.P./S.D.C. rates), healing spell mechanics with server-side dice rolling and cross-document transactions, plus database schema additions. High risk of breakage if any recovery formula, clamp, or healing cast path has bugs.

Re-trigger cubic

…s when H.P. and S.D.C. reach their maximums

Lives in the shared heal path, so every route to full ends the course:
the completing treatment itself, manual heals, and healing casts.
@StreamDemon

Copy link
Copy Markdown
Owner Author

One more refinement from discussion: the treatment course now ends itself when the character is fully mended — once H.P. and S.D.C. both sit at their rolled maximums after any heal (the completing treatment, a manual heal, or a healing cast), current.treatmentDays clears and the next treatment starts a new course at day 1. Implemented in the shared heal path (healPools + a fullyMended predicate); treat skips stamping the day when its own heal completes the mend. 213 tests, verified live.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: Adds new backend mutations, database schema changes, and complex business logic for recovery rates and healing spells. These are high-impact changes affecting character vitals, data integrity, and spell casting — require human review.

Re-trigger cubic

@StreamDemon StreamDemon merged commit 03bcd34 into main Jul 6, 2026
2 checks passed
@StreamDemon StreamDemon deleted the feat/recovery-healing branch July 6, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recovery rates & healing spells: rest/meditation/ley draw, treatment days, spell healing with targets

1 participant